import {
  PerspectiveCamera,
  Scene,
  WebGLRenderer,
  Mesh,
  AmbientLight,
  GridHelper,
  PlaneGeometry,
  Clock,
  AxesHelper,
  Object3D,
  Group,
  BufferGeometry,
  Geometry,
  Matrix3,
  Matrix4,
  DirectionalLight,
  MeshBasicMaterial,
  Color,
  BoxBufferGeometry,
  PointLight,
} from "three";
import Stats from "three/examples/jsm/libs/stats.module";
import { SpotLight } from "three";
import { Fog } from "three";
import { PointerLockControls } from "./lib/PointerLockControls";
import { MeshLambertMaterial } from "three";
import { GLTFLoader, GLTFParser } from "three/examples/jsm/loaders/GLTFLoader";
import { SceneUtils } from "three/examples/jsm/utils/SceneUtils";
// import { FBXLoader } from "three/examples/jsm/loaders/FBXLoader"
import * as CANNON from "cannon-es";

let camera: PerspectiveCamera, scene: Scene, renderer: WebGLRenderer;
let stats: Stats;
let mesh: Mesh;
let group: Group;

let world: CANNON.World;
let sphereBody: CANNON.Body;
let groundBody: CANNON.Body;
let convexBody: CANNON.ConvexPolyhedron;
let control: any;

let timeStep = 1 / 60;
let clock = new Clock();
// Create a slippery material (friction coefficient = 0.0)
let physicsMaterial = new CANNON.Material("slipperyMaterial");
let physicsContactMaterial = new CANNON.ContactMaterial(
  physicsMaterial,
  physicsMaterial,
  {
    friction: 1,
    restitution: 0.3,
  }
);

initCannon();
init();
animate();

function init() {
  stats = Stats();
  camera = new PerspectiveCamera(
    75,
    window.innerWidth / window.innerHeight,
    0.1,
    1000
  );
  // camera.position.copy(sphereBody.position as any);

  scene = new Scene();
  scene.background = new Color("#333");

  var ambient = new AmbientLight("#fff", 1);
  scene.add(ambient);
  var pointLight = new PointLight("#fff", 1);
  scene.add(pointLight);

  let gridHelper = new GridHelper(100, 10);
  scene.add(gridHelper);
  let axesHelper = new AxesHelper(100);
  scene.add(axesHelper);

  control = new PointerLockControls(camera, sphereBody);
  setTimeout(function () {
    control.enabled = true;
  }, 500);
  scene.add(control.getObject());

  // floor
  let geometry = new PlaneGeometry(300, 300, 50, 50);
  let material = new MeshLambertMaterial({ color: 0xdddddd });

  mesh = new Mesh(geometry, material);
  mesh.rotateX(-Math.PI / 2);

  mesh.position.copy(groundBody.position as any);
  mesh.castShadow = true;
  mesh.receiveShadow = true;
  scene.add(mesh);

  // add model
  new GLTFLoader().load(require("../static/modal/well.glb").default, (gltf) => {
    let group = gltf.scene;
    group.traverse((child) => {
      if (child.type === "Mesh") {
        let _child = (child as any);
        _child.material.emissive = new Color(1, 1, 1);
        _child.material.emissiveIntensity = 1;
        _child.material.emissiveMap = _child.material.map;
      }
    });
    scene.add(group);
  });

  // add collison
  new GLTFLoader().load(
    require("../static/modal/well_collision.glb").default,
    (gltf) => {
      group = gltf.scene;

      group.traverse((obj) => {
        if (obj.type === "Mesh") {
          obj.updateMatrix();

          let matrix4 = obj.matrix.clone();
          let bufferGeometry = (obj as Mesh).geometry.clone();
          bufferGeometry.applyMatrix4(matrix4);
          let geometry: Geometry = new Geometry().fromBufferGeometry(
            bufferGeometry as any
          );

          geometry.mergeVertices();
          geometry.computeBoundingSphere();
          geometry.computeFaceNormals();

          const vertices = geometry.vertices.map((v) =>
            new CANNON.Vec3().copy(v as any)
          );
          const faces = geometry.faces.map((f) => [f.a, f.b, f.c]);
          const normals = geometry.faces.map((f) =>
            new CANNON.Vec3().copy(f.normal as any)
          );

          // Construct polyhedron
          const hullPartShape = new CANNON.ConvexPolyhedron({
            vertices,
            faces,
            normals,
          });

          let body = new CANNON.Body({ mass: 0, material: physicsMaterial });
          body.addShape(hullPartShape);
          world.addBody(body);
        }
      });
    }
  );

  renderer = new WebGLRenderer({ antialias: true });
  renderer.setSize(window.innerWidth, window.innerHeight);

  document.body.appendChild(renderer.domElement);
}

function initCannon() {
  // Setup our world
  world = new CANNON.World();
  world.quatNormalizeSkip = 0;
  world.quatNormalizeFast = false;

  var solver = new CANNON.GSSolver();

  world.defaultContactMaterial.contactEquationStiffness = 1e9;
  world.defaultContactMaterial.contactEquationRelaxation = 4;

  solver.iterations = 7;
  solver.tolerance = 0.1;
  var split = true;
  if (split) world.solver = new CANNON.SplitSolver(solver as any);
  else world.solver = solver;

  world.gravity.set(0, -10, 0);
  world.broadphase = new CANNON.NaiveBroadphase();
  world.broadphase.useBoundingBoxes = true;

  // We must add the contact materials to the world
  world.addContactMaterial(physicsContactMaterial);

  let sphereShape = new CANNON.Sphere(0.8);
  sphereBody = new CANNON.Body({ mass: 15, material: physicsMaterial });
  sphereBody.addShape(sphereShape);
  sphereBody.position.set(-11, 9.5, -7.4);
  // sphereBody.position.set(0, 50, 0);
  sphereBody.linearDamping = 0.9;
  world.addBody(sphereBody);

  // Create a plane
  var groundShape = new CANNON.Plane();
  groundBody = new CANNON.Body({ mass: 0, material: physicsMaterial });
  groundBody.addShape(groundShape);
  groundBody.quaternion.setFromAxisAngle(
    new CANNON.Vec3(1, 0, 0),
    -Math.PI / 2
  );
  groundBody.position.set(0, 0, 0);
  world.addBody(groundBody);
}

function animate() {
  requestAnimationFrame(animate);
  stats.update();

  world.step(timeStep);
  control.update(clock.getDelta() * 500);

  renderer.render(scene, camera);
}
